
package viz_du1;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import javax.swing.Action;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
//import viz_du1.ImageLoader;
//import viz_du1.Matrix;


/**
 * Tato třída je určena k demostraci toho jak vytvořit prázdný obrázek, jak měnit
 * jednotlivé pixely v obrázku a jak obrázek zobrazit na obrazovce
 * při změně nějakých parametrů (v tomto případě při změně pozice na posuvníku).
 * 
 * @author Ladislav Čmolík
 */
public class ImageDisplay extends JPanel implements ChangeListener, ActionListener {

    // Konstanty určující velikost obrázku
    private final int IMG_WIDTH = 255;
    private final int IMG_HEIGHT = 255;
    private final int IMG_DEPTH = 113;
    
    private final int FINAL_IMG_WIDTH = 255;
    private final int FINAL_IMG_HEIGHT = 255;
    private final int FINAL_IMG_DEPTH = 500;
    
    private final int NUM_OF_IMAGES = 113;
    private final int Z_SCALE = 2;
    // Konstranty určující barevné mapování
    private final int SPECTRUM = 1;
    private final int GRAY = 2;
    private final int RB = 3;
    // Proměnná určující barevné mapování
    private int mapping = GRAY;
    
    //maximalni hodnota sede v PNG16 obrazku
    int imgMaxGray = 3272;
    //maximalni hodnota v BufferedImage.TYPE_USHORT_GRAY
    int maxGray = 65534;
    
    int slider_value = 0;
    
   
    // Od Toma Altmana_______
    //Aktualni rez
    int actRez; 
    //Transformujici matrix
    Matrix transMatrix;
   //Normala roviny xy
    double[]normal = {0.,0.,1.};
    Matrix normalMatrix = new Matrix(normal);
    
    // Zadana libovolna normala
    double[]randNormal = {1., 1., 0.};
    Matrix randNormalMatrix = new Matrix(randNormal);
    
    // 113 rezu lebky + 113 dopocitanych
    BufferedImage[] images;
   //Aktualni obrazek
    BufferedImage image;
    // Rastr obrázku
    WritableRaster raster;
    

    /**
     * Vytvoří prázdný RGB obrázek kde na každý kanál jednoho pixelu připadá 8 bitů,
     * získá z obrázku rastr a nastaví rozměry JPanelu na velikost obrázku.
     */
    public ImageDisplay() {
        image = new BufferedImage(FINAL_IMG_WIDTH, FINAL_IMG_HEIGHT, 
                    BufferedImage.TYPE_INT_RGB);
        raster = image.getRaster();
        Dimension dim = new Dimension(FINAL_IMG_WIDTH, FINAL_IMG_HEIGHT);
        setMinimumSize(dim);
        setPreferredSize(dim);
        setMaximumSize(dim);
    
        loadImages();
        interpolateImages();
    }
    
    
    
    /**
     * Na základě zadané libovolné normály a výběru řezu, určí transformační matici, 
     * která převede vzorky z roviny xy do roviny zadané libovolnou normálou
     */
    public void computeTransMatrix(){
        randNormalMatrix.normalize();
        
        Matrix rotateAxis = normalMatrix.cross(randNormalMatrix);
       
        Matrix rotateMatrix; 
        if(rotateAxis.get(0,0) == 0 && rotateAxis.get(1,0) == 0 && rotateAxis.get(2,0) == 0){
            rotateMatrix = Matrix.identity(4);
        }else{
            rotateAxis.normalize();
        
        double angle = Math.acos(normalMatrix.dot(randNormalMatrix));
        double cos_angle = Math.cos(angle/2); 
        double sin_angle = Math.sin(angle/2);
        
        //rotacni matice - zdroj: http://en.wikipedia.org/wiki/Euler–Rodrigues_formula
        double a = cos_angle;
        double b = rotateAxis.get(0, 0) * sin_angle;
        double c = rotateAxis.get(1, 0) * sin_angle;
        double d = rotateAxis.get(2, 0) * sin_angle;
        
        double rotate[][] = new double[4][4];
        rotate[0][0] = a*a + b*b - c*c - d*d;
        rotate[0][1] = 2*(b*c - a*d);
        rotate[0][2] = 2*(b*d + a*c); 
        rotate[0][3] = 0.;
        
        rotate[1][0] = 2*(b*c + a*d);
        rotate[1][1] = a*a + c*c - b*b - d*d;
        rotate[1][2] = 2*(c*d - a*b);
        rotate[1][3] = 0.;
        
        rotate[2][0] = 2*(b*d - a*c);
        rotate[2][1] = 2*(c*d + a*b);
        rotate[2][2] = a*a + d*d - b*b - c*c;
        rotate[2][3] = 0.;
        
        rotate[3][0] = 0.;
        rotate[3][1] = 0.;
        rotate[3][2] = 0.;
        rotate[3][3] = 1.;
        
        
       /* double rotate[][] = new double[4][4];
        rotate[0][0] = rotateAxis.get(0, 0)*rotateAxis.get(0, 0)*(1-cos_angle) + cos_angle;
        rotate[0][1] = rotateAxis.get(0, 0)*rotateAxis.get(1, 0)*(1-cos_angle) - rotateAxis.get(2, 0)*sin_angle;
        rotate[0][2] = rotateAxis.get(0, 0)*rotateAxis.get(2, 0)*(1-cos_angle) + rotateAxis.get(1, 0)*sin_angle;
        rotate[0][3] = 0.;
        
        rotate[1][0] = rotateAxis.get(1, 0)*rotateAxis.get(0, 0)*(1-cos_angle) + rotateAxis.get(2, 0)*sin_angle;
        rotate[1][1] = rotateAxis.get(1, 0)*rotateAxis.get(1, 0)*(1-cos_angle) + cos_angle;
        rotate[1][2] = rotateAxis.get(1, 0)*rotateAxis.get(2, 0)*(1-cos_angle) - rotateAxis.get(0, 0)*sin_angle; // opraveno
        rotate[1][3] = 0.;
        
        rotate[2][0] = rotateAxis.get(0, 0)*rotateAxis.get(2, 0)*(1-cos_angle) - rotateAxis.get(1, 0)*sin_angle; // opraveno
        rotate[2][1] = rotateAxis.get(1, 0)*rotateAxis.get(2, 0)*(1-cos_angle) + rotateAxis.get(0, 0)*sin_angle;
        rotate[2][2] = rotateAxis.get(2, 0)*rotateAxis.get(2, 0)*(1-cos_angle) + cos_angle;
        rotate[2][3] = 0.;
        
        rotate[3][0] = 0.;
        rotate[3][1] = 0.;
        rotate[3][2] = 0.;
        rotate[3][3] = 1.;
       */
            rotateMatrix = new Matrix(rotate);
        }
        double transfer[][] = new double[4][4];
        // make identity matrix
        for(int i = 0; i<4; i++){
            for(int j = 0; j<4; j++){
                if(i == j) transfer[i][j]= 1.;
                else transfer[i][j]= 0.;
            }
         }
        // set transfer
       transfer[2][3] = (double)0;
       Matrix transferMatrix = new Matrix(transfer);
       transMatrix = transferMatrix.times(rotateMatrix);
    }
    
    /**
     * Na základě transformovanych souradnic z roviny xy do libovolne roviny
     * vrátí rozinterpolovanou hodnotu z osmi sousedních vrcholů
     * 
     * @param x .... x realna souradnice bodu v krychli - rozsah 0-500
     * @param y .... y realna souradnice bodu v krychli - rozsah 0-500
     * @param z .... z realna souradnice bodu v krychli - musi byt v rozsahu 0-113!
     * 
     * 
     */
    private int interpolatePoint(double x, double y, double z){
        int gray = 0;
        double x_scale = (double) ((double)IMG_WIDTH/(double)FINAL_IMG_WIDTH)*x; 
        double y_scale = (double) ((double)IMG_HEIGHT/(double)FINAL_IMG_HEIGHT)*y;
        double z_scale = z;
        int x0 = (int) Math.floor(x_scale);
        int x1 = (int) Math.ceil(x_scale);
        int y0 = (int) Math.floor(y_scale);
        int y1 = (int) Math.ceil(y_scale);
        int z0 = (int) Math.floor(z_scale);
        int z1 = (int) Math.ceil(z_scale);
        double a = x1 - x_scale;
        double b = z1 - z_scale;
        double c = y_scale - y0;
        /*
         * Hodnoty osmi sousednich pixelu
         * Notace: FLR = Front lower right
         *         BUL = Back upper left
         */
        double FLL = images[z0].getRaster().getSample(x0,y0,0);
        double FLR = images[z0].getRaster().getSample(x1,y0,0);
        double FUR = images[z0].getRaster().getSample(x1,y1,0);
        double FUL = images[z0].getRaster().getSample(x0,y1,0);
        //back face
        double BLL = images[z1].getRaster().getSample(x0,y0,0);
        double BLR = images[z1].getRaster().getSample(x1,y0,0);
        double BUR = images[z1].getRaster().getSample(x1,y1,0);
        double BUL = images[z1].getRaster().getSample(x0,y1,0);
        gray = (int)Math.round((1.-a)*((1.-b)*((1.-c)*FLR+c*FUR)+b*((1.-c)*BLR+c*BUR))+a*((1.-b)*((1.-c)*FLL+c*FUL)+b*((1.-c)*BLL+c*BUL)));
        return gray;
    }
    
    
    
     /**
     * Na základě x,y,z souřadnic v krychli dat vrátí rozinterpolovanou hodnotu z osmi 
     * sousedních vrcholů
     * 
     * @param x .... x souradnice bodu v krychli - rozsah 0-500
     * @param y .... y souradnice bodu v krychli - rozsah 0-500
     * @param z .... z souradnice bodu v krychli - musi byt v rozsahu 0-113!
     * 
     */
    private int interpolatePoint(int x, int y, int z){
        int gray = 0;
        float x_scale = (float) ((float)IMG_WIDTH/(float)FINAL_IMG_WIDTH)*(float)x; 
        float y_scale = (float) ((float)IMG_HEIGHT/(float)FINAL_IMG_HEIGHT)*(float)y;
        float z_scale = z;
        int x0 = (int) Math.floor(x_scale);
        int x1 = (int) Math.ceil(x_scale);
        int y0 = (int) Math.floor(y_scale);
        int y1 = (int) Math.ceil(y_scale);
        int z0 = (int) Math.floor(z_scale);
        int z1 = (int) Math.ceil(z_scale);
        float a = x1 - x_scale;
        float b = z1 - z_scale;
        float c = y_scale - y0;
        /*
         * Hodnoty osmi sousednich pixelu
         * Notace: FLR = Front lower right
         *         BUL = Back upper left
         */
        int FLL = images[z0].getRaster().getSample(x0,y0,0);
        int FLR = images[z0].getRaster().getSample(x1,y0,0);
        int FUR = images[z0].getRaster().getSample(x1,y1,0);
        int FUL = images[z0].getRaster().getSample(x0,y1,0);
        //back face
        int BLL = images[z1].getRaster().getSample(x0,y0,0);
        int BLR = images[z1].getRaster().getSample(x1,y0,0);
        int BUR = images[z1].getRaster().getSample(x1,y1,0);
        int BUL = images[z1].getRaster().getSample(x0,y1,0);
        gray =  Math.round((1-a)*((1-b)*((1-c)*FLR+c*FUR)+b*((1-c)*BLR+c*BUR))+a*((1-b)*((1-c)*FLL+c*FUL)+b*((1-c)*BLL+c*BUL)));
        return gray;
    }
    
    private void loadImages(){
        ImageLoader imageLoader = new ImageLoader();
        images = new BufferedImage[2*NUM_OF_IMAGES];
        
        String number;
        for(int i = 0; i<NUM_OF_IMAGES; i++){
            if(i<9){ 
                number = "00"+ Integer.toString(i+1);
            } else if(i < 99){
                number = "0"+ Integer.toString(i+1);
            } else{
                number = Integer.toString(i+1);
            }
            
            images[2*i] = imageLoader.loadImage("./cthead/cthead-16bit"  + number + ".png");  
        }
    }
    
    private void interpolateImages(){
        WritableRaster rastPrev;
        WritableRaster rastForw;
        for(int i = 1; i<NUM_OF_IMAGES*2-1; i+=2){
             images[i] = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, 
                    BufferedImage.TYPE_USHORT_GRAY);
            rastPrev = images[i-1].getRaster();
            rastForw = images[i+1].getRaster();
            for(int x = 0; x<IMG_WIDTH; x++){
                for(int y = 0; y<IMG_HEIGHT; y++){
                    images[i].getRaster().setSample(x, y, 0, (rastPrev.getSample(x, y, 0) + rastForw.getSample(x, y, 0))/2. );
                }
            }
        }
        // posledni obrazek je stejny jak predposledni
        images[225] = images[224];
    }
    public void setImagePixel(int x, int y, int[] color){
            raster.setPixel(x, y, color);
            image.getRaster().setPixel(x, y, color);
    }
    
    public void setImagePixel(int x, int y, int gray){
            raster.setSample(x, y, 0, gray);
    }
    
     /*
     * Prevede hodnoty od 0 do 3272 na cernobily obrazek
     * 
     * @return - int[] ... index 0-R, 1-G, 2-B
     */
    public int[] PNG16ToBtoW(int gray){
       int[] ret = new int[3];
       float grayScale = (float)gray/(float)imgMaxGray;
        if(gray <= 2000){
            ret[0] = (int) (255*(grayScale));
            ret[1] = (int) (255*(grayScale));
            ret[2] = (int) (255*(grayScale));
        }
        else{
            ret[0] = 255;
            ret[1] = 255;
            ret[2] = 255;
        }
       return ret; 
    }
    
     /*
     * Prevede hodnoty od 0 do 3272 na hodnoty barvy spektra
     * 
     * @return - int[] ... index 0-R, 1-G, 2-B
     */
    public int[] PNG16ToFullSpec(int gray){
        int[] ret = new int[3];
        float grayScale = (float)gray/(float)imgMaxGray;
        if(gray <= 300){
            ret[0] = 255;
            ret[1] = 0;
            ret[2] = (int) (255*(grayScale*7));
        }
        else if(gray > 300 && gray <= 1500){
            ret[0] = 0;
            ret[1] = (int) (255*(grayScale));
            ret[2] = 255;
        }
        else{
            ret[0] = 0;
            ret[1] = 255;
            ret[2] = (int)(255-(255*(grayScale*6)));
        }
        return ret;
    }
    
     /*
     * Prevede hodnoty od 0 do 3272 na cervenozlute mapovani
     * 
     * @return - int[] ... index 0-R, 1-G, 2-B
     */
    public int[] PNG16ToRB(int gray){
        int[] ret = new int[3];
        float grayScale = (float)gray/(float)imgMaxGray;
        if(gray <= 800){
            ret[0] = (int) (128*(grayScale));
            ret[1] = 0;
            ret[2] = 0;
        }
        else if(gray > 800 && gray <= 1600){
            ret[0] = (int) (128+128*(grayScale));
            ret[1] = (int) (128*(grayScale));
            ret[2] = 0;
        }   
        else if(gray > 1600 && gray <= 2400){
            ret[0] = 255;
            ret[1] = (int) (128+128*(grayScale));
            ret[2] = (int) (128*(grayScale));
        }    
        else{
            ret[0] = 255;
            ret[1] = 255;
            ret[2] = 255;
        }
        return ret;
    }
    
     /**
     * Zapíše na danou pozici pixelu [x,y] barvu slozenou z RGB.
     */
    
    public void setImagePixel(int x, int y, int r, int g, int b){
        raster.setSample(x,y,0,r);
        raster.setSample(x,y,1,g);
        raster.setSample(x,y,2,b);
    }
    
    
    /*
     * Slouzi k transformovani souradnic okna do souradnic libovolne osy
     * pouzitim transformacni matice a delenim souradnici w
     * 
     */
    
     public double[][] transCoords(int x, int y, int z){
        double[][] vec = {{x},{y},{z},{1.}};
        Matrix m = new Matrix(vec);
        m = transMatrix.times(m);
        m.divideByW();
        return m.getArray();
    }
    
    
    /*
     * 
     * Vykresli obrazek, ktery se nachazi na urcitem indexu pole obrazku
     * 
     */
    public void generateImage(int index){
        
        computeTransMatrix();
        
        for(int x = 0; x <IMG_WIDTH; x++){
            for(int y = 0; y <IMG_HEIGHT; y++){
                
               double [][] coords = transCoords(x,y,index);
               int[] color = new int[3];
               // System.out.println("coords[0..2][0] "+ coords[0][0] + ", " + coords[1][0] + ", " + coords[2][0]);
              
                coords[2][0] = Math.abs(coords[2][0]);
                coords[1][0] = Math.abs(coords[1][0]);
                coords[0][0] = Math.abs(coords[0][0]);
              
                if(coords[2][0]>225)coords[2][0] = 225;
                
                int picture_value = images[(int)coords[2][0]].getRaster().getSample((int)coords[0][0],(int)coords[1][0],0);
              // int picture_value = interpolatePoint(coords[0][0],coords[1][0],coords[2][0]);
                switch(mapping){
                    case SPECTRUM:
                        color = PNG16ToFullSpec(picture_value);
                        break;
                    case GRAY:
                        color = PNG16ToBtoW(picture_value);
                        break;
                    case RB:
                        color = PNG16ToRB(picture_value);
                        break;
                    default:
                        color = PNG16ToFullSpec(picture_value);
                        break;
                }
                setImagePixel(x, y, color);
            }
        }   
        //raster = images[index].getRaster();
     }
    
    /**
     * Zapíše do každého pi.xelu předané hodnoty.
     * Hodnoty musí být v rozsahu 0 až 255. Pixel 
     * (0,0,0) je černý a pixel (255, 255, 255) je bílý.
     */
    public void generateImage(int r, int g, int b) {
        for(int x = 0; x < IMG_WIDTH; x++) {
            for(int y = 0; y < IMG_HEIGHT; y++) {
                raster.setSample(x, y, 0, r);
                raster.setSample(x, y, 1, g);
                raster.setSample(x, y, 2, b);
            }
        }
    }
    
    public BufferedImage getCurrentImage(){
        return image;
    }
    
    public void setMapping(String newMapping){
        if(newMapping.equals("Černobílé"))
            mapping = GRAY;
        if(newMapping.equals("Barevné spektrum"))
            mapping = SPECTRUM;
        if(newMapping.equals("Červenožluté"))
            mapping = RB;
    }

    /**
     * Vykreslí obrázek do panelu. 
     */
    @Override
    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(image, 0, 0, this);
    }

    /**
     * Implementace rozhraní ChangeListener volá se vždy když se změní hodnota
     * na posuvníku. Na základě hodnoty na posuvníku je překreslen obsah obrázku
     * a obrázek je zobrazen.
     */
    @Override
    public void stateChanged(ChangeEvent e) {
        JSlider slider = (JSlider) e.getSource();
        actRez  = slider.getValue();
        generateImage(actRez);
        // Metoda repaint() způsobí, že se zavolá metoda paint() této třídy.
        repaint();
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
   
}